﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace engine
{
    public class Terrain2
    {
       

        public GraphicsDevice graphics;
        private BasicEffect ef;

        private int terrainWidth;
        private int terrainLength;
        private float[,] heightData;

        private VertexBuffer vertexBuffer = null;
        private IndexBuffer indexBuffer = null;
        private VertexDeclaration terrainVertexDeclaration;

        public Matrix Transform { get; set; }

        private Scene scene;

        public Terrain2()
        {
            graphics = Engine.Instance.Game.GraphicsDevice;
        }

        public void Initialize(Scene scene)
        {
            this.scene = scene;

            ef = new BasicEffect(graphics, null);
            ef.TextureEnabled = true;

            Transform = Matrix.CreateScale(100);

        }

        public void LoadContent()
        {

            LoadVertices();
            LoadTextures();
        }

        private void LoadVertices()
        {

            Texture2D heightMap = Engine.Instance.Content.Load<Texture2D>("Textures\\heightmap"); 
            LoadHeightData(heightMap);


            VertexPositionNormalTexture[] terrainVertices = SetUpTerrainVertices();
            int[] terrainIndices = SetUpTerrainIndices();
            terrainVertices = CalculateNormals(terrainVertices, terrainIndices);
            CopyToTerrainBuffers(terrainVertices, terrainIndices);
            terrainVertexDeclaration = new VertexDeclaration(graphics, VertexPositionNormalTexture.VertexElements);
        }

        public void LoadTextures()
        {

            ef.Texture = Engine.Instance.Content.Load<Texture2D>("Textures\\heightmap");
        }

        public void LoadHeightData(Texture2D heightMap)
        {
            float minimumHeight = float.MaxValue;
            float maximumHeight = float.MinValue;

            terrainWidth = heightMap.Width;
            terrainLength = heightMap.Height;

            Color[] heightMapColors = new Color[terrainWidth * terrainLength];
            heightMap.GetData(heightMapColors);

            heightData = new float[terrainWidth, terrainLength];
            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainLength; y++)
                {
                    heightData[x, y] = heightMapColors[x + y * terrainWidth].R;
                    if (heightData[x, y] < minimumHeight) minimumHeight = heightData[x, y];
                    if (heightData[x, y] > maximumHeight) maximumHeight = heightData[x, y];
                }

            for (int x = 0; x < terrainWidth; x++)
                for (int y = 0; y < terrainLength; y++)
                    heightData[x, y] = (heightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * 30.0f;
        }

       
        public VertexPositionNormalTexture[] SetUpTerrainVertices()
        {
            VertexPositionNormalTexture[] terrainVertices = new VertexPositionNormalTexture[terrainWidth * terrainLength];

            for (int x = 0; x < terrainWidth; x++)
            {
                for (int y = 0; y < terrainLength; y++)
                {
                    terrainVertices[x + y * terrainWidth].Position = new Vector3(x, heightData[x, y], -y);
                    terrainVertices[x + y * terrainWidth].TextureCoordinate = new Vector2((float)x / (float)terrainWidth, (float)y / (float)terrainLength);
                    //terrainVertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / 30.0f;
                    //terrainVertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / 30.0f;
                }
            }

            return terrainVertices;
        }
 
        
        public int[] SetUpTerrainIndices()
        {
            int[] indices = new int[(terrainWidth - 1) * (terrainLength - 1) * 6];
            int counter = 0;
            for (int y = 0; y < terrainLength - 1; y++)
            {
                for (int x = 0; x < terrainWidth - 1; x++)
                {
                    int lowerLeft = x + y * terrainWidth;
                    int lowerRight = (x + 1) + y * terrainWidth;
                    int topLeft = x + (y + 1) * terrainWidth;
                    int topRight = (x + 1) + (y + 1) * terrainWidth;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }

            return indices;
        }

        
        private VertexPositionNormalTexture[] CalculateNormals(VertexPositionNormalTexture[] vertices, int[] indices)
        {
            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal = new Vector3(0, 0, 0);

            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);

                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;
            }

            for (int i = 0; i < vertices.Length; i++)
                vertices[i].Normal.Normalize();

            return vertices;
        }

        public void CopyToTerrainBuffers(VertexPositionNormalTexture[] vertices, int[] indices)
        {
            vertexBuffer = new VertexBuffer(graphics, vertices.Length * VertexPositionNormalTexture.SizeInBytes, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            indexBuffer = new IndexBuffer(graphics, typeof(int), indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
        }

        public void Draw(GameTime gameTime)
        {
            
            graphics.RenderState.CullMode = CullMode.None;

            ef.EnableDefaultLighting();
            ef.World = Transform;
            ef.View = scene.Camera.View;
            ef.Projection = scene.Camera.Projection;


            ef.Begin();

            foreach (EffectPass pass in ef.CurrentTechnique.Passes)
            {

                pass.Begin();
               

                graphics.Vertices[0].SetSource(vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                graphics.Indices = indexBuffer;
                graphics.VertexDeclaration = terrainVertexDeclaration;

                int noVertices = vertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes;
                int noTriangles = indexBuffer.SizeInBytes / sizeof(int) / 3;
                graphics.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, noVertices, 0, noTriangles);

                pass.End();
                
            }

            ef.End();


        }
    }
}
